package puzzle.slilin2.solver;

import puzzle.common.data.HatanException;
import puzzle.common.point.Pnt;
import puzzle.common.solver.BoardRule;
import puzzle.slilin2.data.CellType;
import puzzle.slilin2.data.SlilinBoard;
import tomoto.assertion.AssertUtil;

/**
 * 各セルに独立に適用されるルール。
 * 
 * @author tomoto
 */
abstract class SlilinCellRule implements BoardRule<SlilinBoard> {

	public boolean apply(SlilinBoard b) throws HatanException {
		int x0, y0, x1, y1, dx, dy;

		CellType appliedCellType = appliedCellType();
		if (appliedCellType != null) {
			dx = dy = 2;
			switch (appliedCellType) {
			case NUMBER:
				x0 = y0 = 0;
				break;
			case DOT:
				x0 = y0 = 1;
				break;
			case HLINE:
				x0 = 0;
				y0 = 1;
				break;
			case VLINE:
				x0 = 1;
				y0 = 0;
				break;
			default:
				throw AssertUtil.fail();
			}
		} else {
			x0 = y0 = 0;
			dx = dy = 1;
		}

		if (skipOutside()) {
			if (x0 == 0) {
				x0 += 2;
				x1 = b.width - 1;
			} else {
				x1 = b.width;
			}
			if (y0 == 0) {
				y0 += 2;
				y1 = b.height - 1;
			} else {
				y1 = b.height;
			}
		} else {
			x1 = b.width;
			y1 = b.height;
		}

		boolean processAllCell = !skipUnmodifiedCell();
		boolean isChanged = false;
		boolean[] dirtyFlags = b.getDirtyFlags();
		for (int y = y0; y < y1; y += dy) {
			for (int x = x0; x < x1; x += dx) {
				if (processAllCell || dirtyFlags[x + y * b.width]) {
					Pnt p = new Pnt(x, y);
					isChanged |= apply(b, p);
				}
			}
		}
		return isChanged;
	}

	/**
	 * 適用するセルの種別を指定する。
	 * 
	 * @return 適用するセルの種別。適用するセル種別を特定しない場合はnull。
	 */
	protected abstract CellType appliedCellType();

	/**
	 * 一番外側のセル(番人であり実際の盤面の外)の処理を省略するかどうかを指定する。
	 * 
	 * @return 一番外側のセルの処理を省略するならtrue。
	 */
	protected abstract boolean skipOutside();
	
	/**
	 * 変化の近傍にあるセルだけを処理するかどうかを指定する。
	 * 
	 * @return 変化の近傍にあるセルだけを処理するならtrue。
	 */
	protected abstract boolean skipUnmodifiedCell();

	public abstract boolean apply(SlilinBoard b, Pnt p) throws HatanException;
}
